package reports.directors;

import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;

import coreModel.HIT;
import coreModel.Item;
import coreModel.Product;

public class ProductStatsGenerator
{
	
	/* 
	 * =======================================
	 * 				Global vars
	 * =======================================
	 * 
	 */
	private int 	curSupply, 
					minSupply, 
					maxSupply, 
					usedSupply, 
					addedSupply, 
					maxUsedAge, 
					maxCurAge;
	
	private float 	avgSupply,
					avgUsedAge,
					avgCurAge; 
					
/*
		
			
c-curSupply			The number of Items currently in storage for this Product ...
f-avgSupply			The daily average number of Items in storage for this Product ... 
f-minSupply 		The daily minimum number of Items in storage for this Product ... 
f-maxSupply 		The daily maximum number of Items in storage for this Product ... 
r-usedSupply 		The number of Items for this Product taken out of storage ... 
a-addedSupply 		The number of Items for this Product added to storage ... 
			  											... for the given reporting period.
r-avgUsedAge		For all Items of this Product used during the reporting period, 
the average number 	of days spent in storage before being used. 
	
r-maxUsedAge 		For all Items of this Product used during the reporting period,
 the maximum number of 	days spent in storage before being used.
	
a-avgCurAge			For all Items of this Product currently in storage, 
	the average number of days spent in storage.
	
a-maxCurAge			For all Items of this Product currently in storage, 
	the maximum number of days spent in storage. 

*/
	
	private Product p;
	private int months;
	public static Date endDate;
	private Date startDate;
	private int daysInPeriod;
	
	int [] removedItems;
	int [] addedItems;
	
	/* 
	 * =======================================
	 * 				Constructor(s)
	 * =======================================
	 * 
	 */
	
	public ProductStatsGenerator(Product p, int nMonths)
	{
		if (endDate == null)
			endDate = new Date();
//			ProductStatsGenerator.setEndDate(31, 4, 2012);
		
		curSupply = HIT.getInstance().getItemManager().getAllItemsOfProduct(p).size();
		
		this.p = p;
		this.months = nMonths;
				
		
		initializeDateRanges();
		
		initializeItemLists();
		
		
		int [] addedItems = processCurrentItems();
		int [] removedItems = processRemovedItems(p, nMonths);
		calcFinalStats(addedItems, removedItems);
		
	}


	private void initializeItemLists()
	{
		daysInPeriod = daysBetween(startDate, endDate) + 1;
		
		addedItems = new int[daysInPeriod];
		removedItems = new int[daysInPeriod];
	}


	private void initializeDateRanges()
	{
		endDate = zeroOutDate(endDate);
		startDate = calcStartDate(months);
		if (p.getCreationDate().compareTo(startDate) > 0)
		{
			startDate = zeroOutDate( p.getCreationDate() );
		}
	}
	
	
	/* 
	 * =======================================
	 * 		   Private helper methods 
	 * =======================================
	 * 
	 */
	
	private int[] processCurrentItems()
	{
	
		int beginNoItems = 0;
		int curNoItems = 0;
		
		// get the counts of items added per day
		for (Item i:HIT.getInstance().getItemManager().getAllItemsOfProduct(p))
		{
			Date itemDate = i.getEntryDate();
			curNoItems++;

			// Update current ages, 
			int itemAge = getAge(i);
			avgCurAge = (avgCurAge*(curNoItems-1) + (float)itemAge)/(float)curNoItems;
			maxCurAge = (itemAge>maxCurAge)? itemAge : maxCurAge;
			
			if (itemDate.compareTo(startDate)<0)
			{
				beginNoItems++;
				continue;
			}
//			else if (itemDate.compareTo(startDate)==0)
//			{
//				beginNoItems++;
//			}
			
			int dayNo = calcDayNo(startDate, i.getEntryDate());
			
			try { addedItems[dayNo]++; }
			catch (Exception e) { System.out.println("Error: out of bounds!"); }
			
			addedSupply++;
		}
		
		Collection <Item> removed =
			HIT.getInstance().getItemHistory().getRemovedItemsByProduct(p);
		if (removed != null)
			for (Item i : removed)
			{
				Date itemDate = i.getEntryDate();
				curNoItems++;
				
				int itemAge = getAge(i);
				
				if (itemDate.compareTo(startDate)<0)
				{
					continue;
				}
				
				int dayNo = calcDayNo(startDate, i.getEntryDate());
				
				try { addedItems[dayNo]++; }
				catch (Exception e) { System.out.println("Error: out of bounds!"); };
				
				addedSupply++;
				
			}
		
		
		minSupply = beginNoItems;
		
		return addedItems;
		
	}
	
	private int[] processRemovedItems(Product p, int months)
	{
//		int daysInPeriod = calcDaysInPeriod(months);
		
		
//		Date endDate = zeroOutDate(new Date());
//		Date startDate = calcStartDate(months);
//		
//		if (p.getCreationDate().compareTo(startDate) > 0)
//		{
//			startDate = zeroOutDate( p.getCreationDate() );
//		}
		endDate = zeroOutDate(endDate);
		
		
		int daysInPeriod = daysBetween(startDate, endDate) + 1;
		int [] removedItems = new int[daysInPeriod];
		
		if(HIT.getInstance().getItemHistory().getRemovedItemsByProduct(p)!=null)
		// get the counts of items removed per day
			for (Item i : HIT.getInstance().getItemHistory().getRemovedItemsByProduct(p))
			{
				Date itemDate = i.getExitTime();
				
				if (itemDate.compareTo(startDate)<0)
					continue;
				
				usedSupply++;
	
				// Update stats 
				int itemAge = getAge(i);
				avgUsedAge = (avgUsedAge*(usedSupply - 1) + (float)itemAge)/(float)usedSupply;
				maxUsedAge = (itemAge>maxUsedAge)? itemAge : maxUsedAge;
				
				int dayNo = calcDayNo(startDate, i.getExitTime());
				
				try { removedItems[dayNo]++; }
				catch (Exception e) { System.out.println("Error: out of bounds!"); }
				
			}
		
		return removedItems;
	}
	
	
	private void calcFinalStats(int [] addedItems, int [] removedItems)
	{
		if (addedItems.length != removedItems.length)
			throw new RuntimeException("These arrays should have the same length!");
		
		// loop through days and calc other stats:
		// avgSupply, minSupply, maxSupply
		int daysInPeriod = addedItems.length;
		int curNoItems = minSupply;
		for (int day=0; day<daysInPeriod; day++)
		{
			int added = addedItems[day];
			int removed = removedItems[day];
			curNoItems = curNoItems + added - removed;
			
			if (curNoItems < 0)
				throw new RuntimeException("Current item total " +
						"should not be less than zero!");
			
			avgSupply = (avgSupply*day + (float)curNoItems )/(float)(day+1);
			maxSupply = (curNoItems>maxSupply)? curNoItems : maxSupply;
			if (day == 0)
				minSupply = curNoItems;
			else
				minSupply = (curNoItems<minSupply)? curNoItems : minSupply;
		}
	}
	
	
	/* 
	 * =======================================
	 * 		   Public static methods 
	 * =======================================
	 * 
	 */
	
	// calc the age of the item in days
	public static int getAge(Item i)
	{	
		Date birth = i.getEntryDate();
		birth = zeroOutDate(birth);
		
		Date death = (i.getExitTime() == null) ? endDate : i.getExitTime();
		death = zeroOutDate(death);
		
		
		return daysBetween(birth, death);
	}
	
	// obvious
	public static int daysBetween(Date d1, Date d2)
	{
		 return (int)( (d2.getTime() - d1.getTime() + 1000*60*60 ) / (1000 * 60 * 60 * 24));
	}
	
	// Gives the number of days from today back #months inclusive of end dates

	
	// returns number of days from start to current
	public static int calcDayNo(Date start, Date current)
	{
		Date d1 = zeroOutDate(start);
		Date d2 = zeroOutDate(current);
		
		return daysBetween(d1, d2);
	}
	
	// returns date with zeroed hh:mm:ss:ms
	public static Date zeroOutDate(Date d)
	{
		GregorianCalendar zeroedDate = new GregorianCalendar();
		zeroedDate.setTime(d);
		zeroedDate.set(Calendar.HOUR_OF_DAY, 0);
		zeroedDate.set(Calendar.MINUTE, 0);
		zeroedDate.set(Calendar.SECOND, 0);
		zeroedDate.set(Calendar.MILLISECOND, 0);
		
		return zeroedDate.getTime();
	}
	
	// from the current time go back the number of months
	public static Date calcStartDate(int months)
	{
		Date today = zeroOutDate(endDate);
		GregorianCalendar start = new GregorianCalendar();
		start.setTime(today);
		int newMonth = start.get(Calendar.MONTH) - months;
		start.set(Calendar.MONTH, newMonth);
		
		Date startDate = start.getTime();
		startDate = zeroOutDate(startDate);
		return startDate;
	}
	
	
	/**
	 * See setEndDate(Date);
	 * @return the endDate
	 */
	public static Date getEndDate()
	{
		return endDate;
	}


	/**
	 * The endDate is the last day of a requested period. By default this date is today's date. 
	 * For testing purposes this date can be changed to use a day that is earlier than today.
	 * !! Note: If there are Items or Products added after this endDate the report will NOT be accurate.
	 * @param endDate the endDate to set
	 */
	public static void setEndDate(Date endDate)
	{
		ProductStatsGenerator.endDate = endDate;
	}
	
	/**
	 * The endDate is the last day of a requested period. By default this date is today's date. 
	 * For testing purposes this date can be changed to use a day that is earlier than today.
	 * !! Note: If there are Items or Products added after this endDate the report will NOT be accurate.
	 * @param endDate the endDate to set
	 */
	public static void setEndDate(int date, int month, int year)
	{
		GregorianCalendar cal = new GregorianCalendar();
		cal.set(year, month - 1 , date);
		endDate = cal.getTime();
	}
	
	
	/* 
	 * =======================================
	 * 				Getters
	 * =======================================
	 * 
	 */


	public int getCurSupply()
	{
		return curSupply;
	}

	public int getMinSupply()
	{
		return minSupply;
	}

	public int getMaxSupply()
	{
		return maxSupply;
	}

	public int getUsedSupply()
	{
		return usedSupply;
	}

	public int getAddedSupply()
	{
		return addedSupply;
	}

	public int getMaxUsedAge()
	{
		return maxUsedAge;
	}

	public int getMaxCurAge()
	{
		return maxCurAge;
	}

	public float getAvgSupply()
	{
		return avgSupply;
	}

	public float getAvgUsedAge()
	{
		return avgUsedAge;
	}

	public float getAvgCurAge()
	{
		return avgCurAge;
	}
	
	
}
